// SPDX-License-Identifier: GPL-2.0
/******************************************************************************
 *    NAND Flash Controller V301 Device Driver
 *    Copyright (c) 2009-2010 by Hisilicon.
 *    All rights reserved.
 * ***
 *    written by CaiZhiYong. 2010-11-04
 *
 ******************************************************************************/

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/rawnand.h>
#include <linux/mtd/partitions.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <asm/errno.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/hal/hisilicon.h>
#include <linux/uaccess.h>

#include "mtdcore.h"
#include "hinfc301.h"

struct hinfc_ewe_count g_nand_ewetimes;
EXPORT_SYMBOL(g_nand_ewetimes);

static const char * const pagesize_str[] = {"512", "2K", "4K", "8K", "unknown"};
static const char * const ecctype_5115_string[] = {
	"None", "1bit", "4bit", "unknown", "24bits/1K",
	"unknown", "8bit", "unknown"};
static const char * const ecctype_5116_string[] = {
	"None", "1bit", "4bit", "8bit", "24bits/1K",
	"unknown", "unknown", "unknown"};
static const char * const ecctype_5118_string[] = {
	"None", "8bit", "13bit", "18bit", "24bits",
	"27bits", "32bits", "41bits", "48bits", "60bits", "72bits", "80bits"};
static const char * const ecctype_5610_string[] = {
	"None", "1bit", "4bit", "8bit", "24bit",
	"40bit", "64bit", "unknown"
};

static const char *get_ecctype_str(unsigned int ecctype)
{
	if (hi_kernel_get_chip_id() == HI_CHIP_ID_5118_E ||
		hi_kernel_get_chip_id() == HI_CHIP_ID_5118V2_E)
		return ecctype_5118_string[(ecctype & 0x0F)];
	else if (hi_kernel_get_chip_id() <= HI_CHIP_ID_5115T_E)
		return ecctype_5115_string[(ecctype & 0x0F)];
	else if (hi_kernel_get_chip_id() == HI_CHIP_ID_5610T_E)
		return ecctype_5610_string[(ecctype & 0x0F)];
	else
		return ecctype_5116_string[(ecctype & 0x0F)];
}

static const char *get_pagesize_str(enum page_type pagetype)
{
	return pagesize_str[pagetype];
}

static unsigned int get_pagesize(enum page_type pagetype)
{
	unsigned int pagesize[] = {
		NFC_512B, NFC_2K, NFC_4K, NFC_8K, NFC_16K, NFC_32K, };
	return pagesize[pagetype];
}

static unsigned int get_blocksize(enum block_type blocktype)
{
	unsigned int blocksize[] = {64, 128, 256, 512};
	return blocksize[blocktype];
}

void hinfc301_controller_enable(struct hinfc_host *host, int enable)
{
	unsigned int reg_val = readl(host->sysreg + PERI_CRG30);

	if (enable)
		reg_val |= (PERI_CRG30_CLK_EN | PERI_CRG30_CLK_SEL_99M);
	else
		reg_val &= ~PERI_CRG30_CLK_EN;
	writel(reg_val, (host->sysreg + PERI_CRG30));
}

static void hinfc301_status_check_cond_resched(struct hinfc_host *host)
{
	unsigned int loop = 0;

	while (!(hinfc_read(host, HINFC301_STATUS) & 0x1)) {
		if (loop++ > HINFC301_CHECK_STATUS_LOOP_MAX) {
			pr_warn("HINFC301: controller operation time out, CS%d!\n",
				host->chipselect);
			return;
		}
		_cond_resched();
	}
}

static void hinfc301_dma_transfer(struct hinfc_host *host, int todev)
{
	unsigned long reg_val;

	hinfc_write(host, host->dma_buffer, HINFC301_DMA_ADDR_DATA);
	hinfc_write(host, host->dma_oob, HINFC301_DMA_ADDR_OOB);

	if (host->ecctype == et_ecc_none) {
		hinfc_write(host,
			((host->oobsize & HINFC301_DMA_LEN_OOB_MASK) << HINFC301_DMA_LEN_OOB_SHIFT),
			HINFC301_DMA_LEN);

		if (hi_kernel_get_chip_id() != HI_CHIP_ID_MPW_E) {
			hinfc_write(host,
				HINFC301_DMA_PARA_DATA_RW_EN |
				HINFC301_DMA_PARA_OOB_RW_EN |
				((host->n24bit_ext_len &
					HINFC301_DMA_PARA_EXT_LEN_MASK) <<
					HINFC301_DMA_PARA_EXT_LEN_SHIFT),
				HINFC301_DMA_PARA);
		} else {
			hinfc_write(host,
				HINFC301_DMA_PARA_DATA_RW_EN | HINFC301_DMA_PARA_OOB_RW_EN,
				HINFC301_DMA_PARA);
		}
	} else {
		if (hi_kernel_get_chip_id() != HI_CHIP_ID_MPW_E) {
			hinfc_write(host,
				HINFC301_DMA_PARA_DATA_RW_EN | HINFC301_DMA_PARA_OOB_RW_EN |
				HINFC301_DMA_PARA_DATA_EDC_EN | HINFC301_DMA_PARA_OOB_EDC_EN |
				HINFC301_DMA_PARA_DATA_ECC_EN | HINFC301_DMA_PARA_OOB_ECC_EN |
				((host->n24bit_ext_len &
					HINFC301_DMA_PARA_EXT_LEN_MASK) <<
					HINFC301_DMA_PARA_EXT_LEN_SHIFT),
				HINFC301_DMA_PARA);
		} else {
			hinfc_write(host,
				HINFC301_DMA_PARA_DATA_RW_EN | HINFC301_DMA_PARA_OOB_RW_EN |
				HINFC301_DMA_PARA_DATA_EDC_EN | HINFC301_DMA_PARA_OOB_EDC_EN |
				HINFC301_DMA_PARA_DATA_ECC_EN | HINFC301_DMA_PARA_OOB_ECC_EN,
				HINFC301_DMA_PARA);
		}
	}

	reg_val = (HINFC301_DMA_CTRL_DMA_START | HINFC301_DMA_CTRL_BURST4_EN |
		HINFC301_DMA_CTRL_BURST8_EN | HINFC301_DMA_CTRL_BURST16_EN |
		HINFC301_DMA_CTRL_DATA_AREA_EN | HINFC301_DMA_CTRL_OOB_AREA_EN |
		((host->addr_cycle == 4 ? 1 : 0) << HINFC301_DMA_CTRL_ADDR_NUM_SHIFT) |
		((host->chipselect & HINFC301_DMA_CTRL_CS_MASK) << HINFC301_DMA_CTRL_CS_SHIFT));

	if (todev)
		reg_val |= HINFC301_DMA_CTRL_WE;

	wmb(); /* set barrier before writing to host */
	hinfc_write(host, reg_val, HINFC301_DMA_CTRL);

	hi_reg_status_check(host, HINFC301_DMA_CTRL);
}

static void hinfc301_chip_reset(struct hinfc_host *host)
{
	hinfc_write(host, NAND_CMD_RESET, HINFC301_CMD);
	hinfc_write(host,
		(HINFC301_OP_CMD1_EN |
		((host->chipselect & HINFC301_OP_NF_CS_MASK) << HINFC301_OP_NF_CS_SHIFT) |
		HINFC301_OP_WAIT_READY_EN),
		HINFC301_OP);

	hinfc301_status_check_cond_resched(host);
}

int hinfc301_chip_reset_test(struct hinfc_host *host)
{
	hinfc_write(host, NAND_CMD_RESET, HINFC301_CMD);
	hinfc_write(host,
		(HINFC301_OP_CMD1_EN |
		((host->chipselect & HINFC301_OP_NF_CS_MASK) << HINFC301_OP_NF_CS_SHIFT) |
		HINFC301_OP_WAIT_READY_EN),
		HINFC301_OP);
	if (!hi_reg_status_check_mdelay(host, HINFC301_STATUS))
		return  0;

	return 1;
}

static void hinfc301_cmd_ctrl(struct nand_chip *chip, int dat, unsigned int ctrl)
{
	struct hinfc_host *host = (struct hinfc_host *)chip->priv;
	unsigned int ui_page_id;
	unsigned int NFC_CON;

	if (ctrl & NAND_ALE) {
		unsigned int addr_value = 0;
		unsigned int addr_offset = 0;

		if (ctrl & NAND_CTRL_CHANGE) {
			host->addr_cycle = 0x0;
			host->addr_value[0] = 0x0;
			host->addr_value[1] = 0x0;
		}
		addr_offset =  host->addr_cycle << 3;

		if (host->addr_cycle >= HINFC301_ADDR_CYCLE_MASK) {
			addr_offset = (host->addr_cycle - HINFC301_ADDR_CYCLE_MASK) << 3;
			addr_value = 1;
		}

		host->addr_value[addr_value] |= (((unsigned int)dat & 0xff) << addr_offset);

		host->addr_cycle++;
	}

	if ((ctrl & NAND_CLE) && (ctrl & NAND_CTRL_CHANGE)) {
		host->command = (unsigned int)dat & 0xff;
		switch (host->command) {
		case NAND_CMD_PAGEPROG:
			/* set 5610t page0 as 24bit ecc */
			ui_page_id = ((host->addr_value[0] & 0xffff0000) >> 16) +
				host->addr_value[1];
			if ((ui_page_id == 0x0) &&
					(hi_kernel_get_chip_id() == HI_CHIP_ID_5610T_E)) {
				NFC_CON = host->NFC_CON;
				NFC_CON &= ~(HINFC301_CON_PAGESIZE_MASK <<
					HINFC301_CON_ECCTYPE_SHIFT);
				NFC_CON |= et_ecc_5610_24bit1k << HINFC301_CON_ECCTYPE_SHIFT;
				hinfc_write(host, NFC_CON, HINFC301_CON);
			} else {
				hinfc_write(host, host->NFC_CON, HINFC301_CON);
			}

			hinfc_write(host, host->addr_value[0] & 0xffff0000, HINFC301_ADDRL);
			hinfc_write(host, host->addr_value[1], HINFC301_ADDRH);
			hinfc_write(host, NAND_CMD_PAGEPROG << 8 | NAND_CMD_SEQIN, HINFC301_CMD);

			hinfc301_dma_transfer(host, 1);

			/* recover 5610t product ecc setting */
			if ((ui_page_id == 0x0) &&
				(hi_kernel_get_chip_id() == HI_CHIP_ID_5610T_E))
				hinfc_write(host, host->NFC_CON, HINFC301_CON);
			break;
		case NAND_CMD_READSTART:
			hinfc_write(host, HINFC301_INTCLR_UE, HINFC301_INTCLR);
			hinfc_write(host, host->NFC_CON, HINFC301_CON);
			hinfc_write(host, host->addr_value[0] & 0xffff0000, HINFC301_ADDRL);
			hinfc_write(host, host->addr_value[1], HINFC301_ADDRH);
			hinfc_write(host, NAND_CMD_READSTART << 8 | NAND_CMD_READ0, HINFC301_CMD);
			hinfc_write(host, 0, HINFC301_LOG_READ_ADDR);
			hinfc_write(host, (host->pagesize + host->oobsize), HINFC301_LOG_READ_LEN);

			hinfc301_dma_transfer(host, 0);
			host->uc_er = ((hinfc_read(host, HINFC301_INTS) & HINFC301_INTS_UE) ?
				1 : 0);

			host->uc_er = 0;
			break;
		case NAND_CMD_ERASE2:
			hinfc_write(host, host->NFC_CON_ECC_NONE, HINFC301_CON);
			hinfc_write(host, host->addr_value[0], HINFC301_ADDRL);
			hinfc_write(host, (NAND_CMD_ERASE2 << 8) | NAND_CMD_ERASE1, HINFC301_CMD);

			hinfc_write(host,
				HINFC301_OP_WAIT_READY_EN |
				HINFC301_OP_CMD2_EN |
				HINFC301_OP_CMD1_EN |
				HINFC301_OP_ADDR_EN |
				((host->chipselect &
					HINFC301_OP_NF_CS_MASK) <<
					HINFC301_OP_NF_CS_SHIFT) |
				((host->addr_cycle &
					HINFC301_OP_ADDR_CYCLE_MASK) <<
					HINFC301_OP_ADDR_CYCLE_SHIFT),
				HINFC301_OP);

			hinfc301_status_check_cond_resched(host);

			/* grand total the number of nand flash erasing */
			g_nand_ewetimes.ui_erasetimes++;
			break;
		case NAND_CMD_READID:
			memset((unsigned char *)(chip->legacy.IO_ADDR_R), 0, 0x10);

			hinfc_write(host, host->NFC_CON_ECC_NONE, HINFC301_CON);
			hinfc_write(host, NAND_CMD_READID, HINFC301_CMD);
			hinfc_write(host, 0, HINFC301_ADDRL);
			hinfc_write(host, HINFC301_OP_CMD1_EN | HINFC301_OP_ADDR_EN |
				HINFC301_OP_READ_DATA_EN | HINFC301_OP_WAIT_READY_EN |
				((host->chipselect &
					HINFC301_OP_NF_CS_MASK) <<
					HINFC301_OP_NF_CS_SHIFT) |
				(1 << HINFC301_OP_ADDR_CYCLE_SHIFT),
				HINFC301_OP);

			host->addr_cycle = 0x0;

			hinfc301_status_check_cond_resched(host);
			break;
		case NAND_CMD_STATUS:
			hinfc_write(host, host->NFC_CON_ECC_NONE, HINFC301_CON);
			hinfc_write(host, NAND_CMD_STATUS, HINFC301_CMD);
			hinfc_write(host,
				HINFC301_OP_CMD1_EN |
				HINFC301_OP_READ_DATA_EN |
				HINFC301_OP_WAIT_READY_EN |
				((host->chipselect &
					HINFC301_OP_NF_CS_MASK) <<
					HINFC301_OP_NF_CS_SHIFT),
				HINFC301_OP);

			hinfc301_status_check_cond_resched(host);
			break;
		case NAND_CMD_SEQIN:
		case NAND_CMD_ERASE1:
		case NAND_CMD_READ0:
			break;
		case NAND_CMD_RESET:
			hinfc301_chip_reset(host);
			break;
		default:
			break;
		}
	}

	if ((dat == NAND_CMD_NONE) && host->addr_cycle) {
		if (host->command == NAND_CMD_SEQIN || host->command == NAND_CMD_READ0 ||
			host->command == NAND_CMD_READID) {
			host->offset = 0x0;
			host->column = (host->addr_value[0] & 0xffff);
		}
	}
}

static int hinfc301_dev_ready(struct nand_chip *chip)
{
	return 0x1;
}

static void hinfc301_select_chip(struct nand_chip *chip, int chipselect)
{
	struct hinfc_host *host = chip->priv;

	if (chipselect < 0)
		return;

	if (chipselect > CONFIG_HINFC301_MAX_CHIP)
		DBG_BUG("invalid chipselect: %d\n", chipselect);

	host->chipselect = chipselect;
}

static void hinfc301_read_buf(struct nand_chip *chip, uint8_t *buf, int len)
{
	struct hinfc_host *host = chip->priv;

	memcpy(buf, host->buffer + host->column + host->offset, len);
	host->offset += len;

	if (hinfc_read(host, HINFC301_INTS) & 0x40)
		g_nand_ewetimes.ui_unco_ecc_err++;
	if (hinfc_read(host, HINFC301_INTS) & 0x20)
		g_nand_ewetimes.ui_co_ecc_err++;

	hinfc_write(host, HINFC301_INTCLR, 0x60);
}

static int nand_ecc_default_ooblayout_free(
	struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion)
{
	if (section > 1)
		return -ERANGE;
	oobregion->offset = 2;
	oobregion->length = 30;
	return 0;
}

static int nand_ecc_2K_1bit_ooblayout_free(
	struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion)
{
	if (section > 1)
		return -ERANGE;
	oobregion->offset = 22;
	oobregion->length = 30;
	return 0;
}

static int nand_ecc_2K_4bytes_ooblayout_free(
	struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion)
{
	if (section > 1)
		return -ERANGE;
	oobregion->offset = 2;
	oobregion->length = 22;
	return 0;
}

static int nand_ecc_2K_16bits_ooblayout_free(
	struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion)
{
	if (section > 1)
		return -ERANGE;
	oobregion->offset = 2;
	oobregion->length = 6;
	return 0;
}

static struct mtd_ooblayout_ops nand_ecc_default_ooblayout_ops = {
	.free = nand_ecc_default_ooblayout_free,
};
static struct mtd_ooblayout_ops nand_ecc_2K_1bit_ooblayout_ops = {
	.free = nand_ecc_2K_1bit_ooblayout_free,
};
#ifdef CONFIG_HINFC301_HARDWARE_PAGESIZE_ECC
static struct mtd_ooblayout_ops nand_ecc_2K_4bytes_ooblayout_ops = {
	.free = nand_ecc_2K_4bytes_ooblayout_free,
};
static struct mtd_ooblayout_ops nand_ecc_2K_16bits_ooblayout_ops = {
	.free = nand_ecc_2K_16bits_ooblayout_free,
};
#endif

static struct page_page_ecc_info page_page_ecc_5115_5116_info[] = {
	{pt_pagesize_8K, et_ecc_24bit1k, 368, &nand_ecc_default_ooblayout_ops},
	{pt_pagesize_8K, et_ecc_none,    32,  &nand_ecc_default_ooblayout_ops},

	{pt_pagesize_4K, et_ecc_24bit1k, 200, &nand_ecc_default_ooblayout_ops},
	{pt_pagesize_4K, et_ecc_4bytes,  128, &nand_ecc_default_ooblayout_ops},
	/* add  4K - 128 - 8 (Proposed support, need to analyze the OOB area is enough?) */
	{pt_pagesize_4K, et_ecc_8bytes,  128, &nand_ecc_default_ooblayout_ops},
	{pt_pagesize_4K, et_ecc_1bit,    128, &nand_ecc_default_ooblayout_ops},
	{pt_pagesize_4K, et_ecc_none,    32,  &nand_ecc_default_ooblayout_ops},

	{pt_pagesize_2K, et_ecc_1bit,    64,  &nand_ecc_2K_1bit_ooblayout_ops},
	/*
	 * 2k4bytes not support yaffs2 file system, it's not compatibility.
	 * only hardward config mode allow support 2k4Bytes,
	 */
#ifdef CONFIG_HINFC301_HARDWARE_PAGESIZE_ECC
	{pt_pagesize_2K, et_ecc_4bytes,  64,  &nand_ecc_2K_4bytes_ooblayout_ops},
	/* 5116 chip 8bit / 512bytes ECC walking branch */
	{pt_pagesize_2K, et_ecc_8bytes,  64,  &nand_ecc_2K_16bits_ooblayout_ops},
	/* 5115 chip 8bit / 512bytes ECC walking branch */
	{pt_pagesize_2K, et_ecc_16bit1k,  64,  &nand_ecc_2K_16bits_ooblayout_ops},
#endif
	{pt_pagesize_2K, et_ecc_none,    32,  &nand_ecc_default_ooblayout_ops},

	{0, 0, 0, NULL},
};

static struct page_page_ecc_info page_page_ecc_5610_info[] = {
	{pt_pagesize_8K, et_ecc_5610_24bit1k, 368, &nand_ecc_default_ooblayout_ops},
	{pt_pagesize_8K, et_ecc_5610_none,    32,  &nand_ecc_default_ooblayout_ops},

	{pt_pagesize_4K, et_ecc_5610_24bit1k, 200, &nand_ecc_default_ooblayout_ops},
	/*4K - 128 - 4*/
	{pt_pagesize_4K, et_ecc_5610_4bytes,  128, &nand_ecc_default_ooblayout_ops},
	/*4K - 128 - 8*/
	{pt_pagesize_4K, et_ecc_5610_8bytes,  128, &nand_ecc_default_ooblayout_ops},
	{pt_pagesize_4K, et_ecc_5610_1bit,    128, &nand_ecc_default_ooblayout_ops},
	{pt_pagesize_4K, et_ecc_5610_none,    32,  &nand_ecc_default_ooblayout_ops},

	{pt_pagesize_2K, et_ecc_5610_1bit,    64,  &nand_ecc_2K_1bit_ooblayout_ops},
	/*
	 * 2k4bytes not support yaffs2 file system, it's not compatibility.
	 * only hardward config mode allow support 2k4Bytes,
	 */
#ifdef CONFIG_HINFC301_HARDWARE_PAGESIZE_ECC
	{pt_pagesize_2K, et_ecc_5610_4bytes,  64,  &nand_ecc_2K_4bytes_ooblayout_ops},
	{pt_pagesize_2K, et_ecc_5610_8bytes,  64,  &nand_ecc_2K_16bits_ooblayout_ops},   /*5116*/
#endif
	{pt_pagesize_2K, et_ecc_5610_none,    32,  &nand_ecc_default_ooblayout_ops},

	{0, 0, 0, NULL},
};

static struct page_page_ecc_info page_page_ecc_5118_info[] = {
#ifdef CONFIG_HINFC301_HARDWARE_PAGESIZE_ECC
	{pt_pagesize_2K, et_ecc_5118_8bit,  64,  &nand_ecc_2K_4bytes_ooblayout_ops},
#endif
	{0, 0, 0, NULL},
};

/* used the best correct arithmetic. */
struct page_page_ecc_info *hinfc301_get_best_ecc(struct mtd_info *mtd)
{
	struct page_page_ecc_info *best = NULL;
	struct page_page_ecc_info *info = NULL;

	if (hi_kernel_get_chip_id() == HI_CHIP_ID_5118_E ||
		 hi_kernel_get_chip_id() == HI_CHIP_ID_5118V2_E)
		info = page_page_ecc_5118_info;
	else if (hi_kernel_get_chip_id() == HI_CHIP_ID_5610T_E)
		info = page_page_ecc_5610_info;
	else
		info = page_page_ecc_5115_5116_info;

	for (; info->layout; info++) {
		if (get_pagesize(info->pagetype) != mtd->writesize)
			continue;

		if (mtd->oobsize < info->oobsize)
			continue;

		if (!best || (best->ecctype < info->ecctype))
			best = info;
	}

	if (!best)
		DBG_BUG("not support this pagesize(%d) and oobsize(%d).\n",
			mtd->writesize, mtd->oobsize);

	return best;
}

/* force the pagesize and ecctype */
struct page_page_ecc_info *hinfc301_force_ecc(struct mtd_info *mtd,
	enum page_type pagetype, unsigned int ecctype, char *cfgmsg, int allow_pagediv)
{
	int pagesize;
	struct page_page_ecc_info *fit = NULL;
	struct page_page_ecc_info *info = NULL;

	if (hi_kernel_get_chip_id() == HI_CHIP_ID_5118_E ||
		hi_kernel_get_chip_id() == HI_CHIP_ID_5118V2_E)
		info = page_page_ecc_5118_info;
	else if (hi_kernel_get_chip_id() == HI_CHIP_ID_5610T_E)
		info = page_page_ecc_5610_info;
	else
		info = page_page_ecc_5115_5116_info;

	for (; info->layout; info++) {
		if (info->pagetype == pagetype && info->ecctype == ecctype) {
			fit = info;
			break;
		}
	}

	if (!fit) {
		DBG_BUG("system not support this nand %s: pagesize:%s, ecctype:%s\n",
			cfgmsg, get_pagesize_str(pagetype), get_ecctype_str(ecctype));
		return NULL;
	}

	pagesize = get_pagesize(pagetype);
	if ((pagesize != mtd->writesize) && (pagesize > mtd->writesize || !allow_pagediv)) {
		DBG_BUG("%s is inconsistent, config pagesize %d, ", cfgmsg, pagesize);
		DBG_BUG("but the nand chip pageszie is %d\n", mtd->writesize);
		return NULL;
	}

	if (fit->oobsize > mtd->oobsize) {
		DBG_BUG("%s is inconsistent, config %s require oobsize(%d), ",
			cfgmsg, get_ecctype_str(ecctype), fit->oobsize);
		DBG_BUG("but the nand chip oobsize is %d, littler than config require.\n",
			mtd->oobsize);
		return NULL;
	}

	return fit;
}

static void hinfc301_ecc_probe(struct mtd_info *mtd, struct nand_chip *chip)
{
	const char *start_type = "unknown";
	struct page_page_ecc_info *best = NULL;
	struct hinfc_host *host = chip->priv;
	uint con_pagesize_mask;
	uint con_ready_busy_sel;
	uint con_ecctype_shift;
	uint con_ecctype_mask;

	con_pagesize_mask = HINFC301_CON_PAGESIZE_MASK;
	con_ready_busy_sel = HINFC301_CON_READY_BUSY_SEL;
	con_ecctype_shift = HINFC301_CON_ECCTYPE_SHIFT;

	if (hi_kernel_get_chip_id() == HI_CHIP_ID_5118_E ||
		hi_kernel_get_chip_id() == HI_CHIP_ID_5118V2_E)
		con_ecctype_mask = HINFC301_CON_ECCTYPE_MASK_5118;
	else
		con_ecctype_mask = HINFC301_CON_ECCTYPE_MASK;

#ifdef CONFIG_HINFC301_AUTO_PAGESIZE_ECC
	best = hinfc301_get_best_ecc(mtd);
	start_type = "Auto";
#endif

#ifdef CONFIG_HINFC301_HARDWARE_PAGESIZE_ECC
	best = hinfc301_force_ecc(mtd,
		((host->NFC_CON >> HINFC301_CON_PAGEISZE_SHIFT) & con_pagesize_mask),
		((host->NFC_CON >> con_ecctype_shift) & con_ecctype_mask),
		"hardware config", 0);
	start_type = "Hardware";
#endif

#ifdef CONFIG_HINFC301_PAGESIZE_AUTO_ECC_NONE
	{
		int pagetype;

		switch (mtd->writesize) {
		case NFC_512B:
			pagetype = pt_pagesize_512;
			break;
		case NFC_2K:
			pagetype = pt_pagesize_2K;
			break;
		case NFC_4K:
			pagetype = pt_pagesize_4K;
			break;
		case NFC_8K:
			pagetype = pt_pagesize_8K;
			break;
		default:
			pagetype = pt_pagesize_2K;
			break;
		}
		best = hinfc301_force_ecc(mtd, pagetype, et_ecc_none, "force config", 0);
		start_type = "AutoForce";
	}
#endif

	if (!best)
		DBG_BUG("Please select nand flash pagesize and ecctype!\n");

	if (best->ecctype != et_ecc_none)
		mtd->oobsize = best->oobsize;
	mtd->ooblayout = best->layout;
	host->ecctype = best->ecctype;
	host->pagesize = get_pagesize(best->pagetype);
	host->oobsize = mtd->oobsize;

	if (hi_kernel_get_chip_id() != HI_CHIP_ID_5118_E &&
		hi_kernel_get_chip_id() != HI_CHIP_ID_5118V2_E) {
		if (host->ecctype == et_ecc_24bit1k) {
			if (host->pagesize == NFC_4K)
				host->n24bit_ext_len = 0x03; /* 8bytes; */
			else if (host->pagesize == NFC_8K)
				host->n24bit_ext_len = 0x01; /* 4bytes; */
		}
	}

	host->dma_oob = host->dma_buffer + host->pagesize;

	host->NFC_CON = (HINFC301_CON_OP_MODE_NORMAL |
		(((unsigned int)best->pagetype & con_pagesize_mask) <<
			HINFC301_CON_PAGEISZE_SHIFT) |
		con_ready_busy_sel | ((best->ecctype & con_ecctype_mask) << con_ecctype_shift));

	host->NFC_CON_ECC_NONE = (HINFC301_CON_OP_MODE_NORMAL |
		(((unsigned int)best->pagetype & con_pagesize_mask) <<
			HINFC301_CON_PAGEISZE_SHIFT) |
		con_ready_busy_sel);

	if (mtd->writesize > NAND_MAX_PAGESIZE || mtd->oobsize > NAND_MAX_OOBSIZE)
		DBG_BUG("kernel not support this NAND.\n");
		DBG_BUG("please increase NAND_MAX_PAGESIZE and NAND_MAX_OOBSIZE.\n");

	if (mtd->writesize != host->pagesize) {
		unsigned int shift = 0;
		unsigned int writesize = mtd->writesize;

		while (writesize > host->pagesize) {
			writesize >>= 1;
			shift++;
		}
		mtd->size = mtd->size >> shift;
		mtd->erasesize = mtd->erasesize >> shift;
		mtd->writesize = host->pagesize;
		pr_info("Nand divide into 1/%u\n", (1 << shift));
	}

	pr_info("Nand(%s): ", start_type);
	pr_info("Block:%sB ", int_to_size(mtd->erasesize));
	pr_info("Page:%sB ",  int_to_size(mtd->writesize));
	pr_info("Chip:%sB ",  int_to_size(mtd->size));
	pr_info("OOB:%sB ",   int_to_size(mtd->oobsize));
	pr_info("ECC:%s ",   get_ecctype_str(host->ecctype));
	pr_info("\n");
}

int hinfc301_nand_init(struct hinfc_host *host, struct nand_chip *chip)
{
	uint con_ecctype_shift;
	uint con_ecctype_mask;
	enum page_type pagetype;
	enum block_type blocktype;
	const uint con_ready_busy_sel = HINFC301_CON_READY_BUSY_SEL;

	con_ecctype_shift = HINFC301_CON_ECCTYPE_SHIFT;

	if (hi_kernel_get_chip_id() == HI_CHIP_ID_5118_E ||
		hi_kernel_get_chip_id() == HI_CHIP_ID_5118V2_E)
		con_ecctype_mask = HINFC301_CON_ECCTYPE_MASK_5118;
	else
		con_ecctype_mask = HINFC301_CON_ECCTYPE_MASK;

	chip->priv = host;
	chip->legacy.cmd_ctrl = hinfc301_cmd_ctrl;
	chip->legacy.dev_ready = hinfc301_dev_ready;
	chip->legacy.select_chip = hinfc301_select_chip;
	chip->legacy.read_byte = hi_read_byte;
	//chip->legacy.read_word = hi_read_word;
	chip->legacy.write_buf = hi_write_buf;
	chip->legacy.read_buf = hinfc301_read_buf;
	chip->legacy.chip_delay = HINFC301_CHIP_DELAY;
	chip->options = NAND_SKIP_BBTSCAN;
	//chip->ecc.layout = NULL;
	chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_NONE;

	host->chip = chip;
	host->addr_cycle = 0;
	host->addr_value[0] = 0;
	host->addr_value[1] = 0;
	host->chipselect = 0;

	host->NFC_CON = (hinfc_read(host, HINFC301_CON) |
								HINFC301_CON_OP_MODE_NORMAL |
								con_ready_busy_sel);

	host->NFC_CON_ECC_NONE = (host->NFC_CON &
		(~(con_ecctype_mask << con_ecctype_shift)));

	/* hardware config */
	pagetype = (hinfc_read(host, HINFC301_CON) >> HINFC301_CON_PAGEISZE_SHIFT) &
		HINFC301_CON_PAGESIZE_MASK;

	host->hw_pagesize = get_pagesize(pagetype);

	blocktype = hinfc_read(host, HINFC301_NFC_BOOT_SET) & 0x3;

	host->hw_blocksize = get_blocksize(blocktype) * host->hw_pagesize;

	host->buffer = dma_alloc_coherent(host->dev, (NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE),
		&host->dma_buffer, GFP_KERNEL);

	if (!host->buffer)
		return 1;

	memset((char *)chip->legacy.IO_ADDR_R, 0xff, HINFC301_BUFFER_BASE_ADDRESS_LEN);
	memset(host->buffer, 0xff, (NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE));

	hinfc_write(host,
		SET_HINFC301_PWIDTH(CONFIG_HINFC301_W_LATCH,
			CONFIG_HINFC301_R_LATCH, CONFIG_HINFC301_RW_LATCH),
		HINFC301_PWIDTH);

	nand_base_oob_resize = hinfc301_ecc_probe;

	return 0;
}

#ifdef CONFIG_PM
int hinfc301_suspend(struct platform_device *pltdev, pm_message_t state)
{
	struct hinfc_host *host = platform_get_drvdata(pltdev);

	hinfc301_status_check_cond_resched(host);

	hi_reg_status_check(host, HINFC301_DMA_CTRL);

	hinfc301_controller_enable(host, 0);

	return 0;
}

int hinfc301_resume(struct platform_device *pltdev)
{
	struct hinfc_host *host = platform_get_drvdata(pltdev);

	hinfc301_controller_enable(host, 1);
	hinfc301_chip_reset(host);
	hinfc_write(host, SET_HINFC301_PWIDTH(CONFIG_HINFC301_W_LATCH,
		CONFIG_HINFC301_R_LATCH, CONFIG_HINFC301_RW_LATCH), HINFC301_PWIDTH);
	return 0;
}
#endif

MODULE_IMPORT_NS(HW_RTOS_NS);
